package com.xiaohe66.web.application.sys.sec;

import com.xiaohe66.common.ex.BusinessException;
import com.xiaohe66.common.util.Assert;
import com.xiaohe66.common.util.RsaUtils;
import com.xiaohe66.common.util.SystemClock;
import com.xiaohe66.common.value.ErrorCodeEnum;
import com.xiaohe66.common.web.validator.annotation.Verify;
import com.xiaohe66.web.application.sys.sec.bo.SecEncryptLoginBo;
import com.xiaohe66.web.application.sys.sec.bo.SecSecretKeyChangeBo;
import com.xiaohe66.web.domain.account.aggregate.Account;
import com.xiaohe66.web.domain.account.repository.AccountRepository;
import com.xiaohe66.web.domain.account.value.AccountName;
import com.xiaohe66.web.domain.sys.sec.agg.SecEncrypt;
import com.xiaohe66.web.domain.sys.sec.entity.CurrentAccount;
import com.xiaohe66.web.domain.sys.sec.repository.SecEncryptRepository;
import com.xiaohe66.web.domain.sys.sec.repository.SysConfigRepository;
import com.xiaohe66.web.domain.sys.sec.service.SecEncryptService;
import com.xiaohe66.web.domain.sys.sec.service.SecurityService;
import com.xiaohe66.web.domain.sys.sec.value.SecEncryptPublicKey;
import com.xiaohe66.web.domain.sys.sec.value.SysSecretKey;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * @author He
 * @since 2023.12.27 15:45
 */
@Service
@RequiredArgsConstructor
public class SecEncryptLoginAppService {

    private static final long LOGIN_EXPIRATION_MS = 3 * 60 * 1000;

    private final AccountRepository accountRepository;
    private final LoginService loginService;
    private final SecurityService securityService;
    private final SysConfigRepository sysConfigRepository;
    private final SecEncryptRepository sysEncryptRepository;
    private final SecEncryptService sysEncryptService;

    @Verify
    public String login(SecEncryptLoginBo bo) {

        long currentTimeMillis = SystemClock.currentTimeMillis();

        if (currentTimeMillis - bo.getTimestamp() > LOGIN_EXPIRATION_MS) {
            throw new BusinessException(ErrorCodeEnum.ILLEGAL_OPERATE, "[timestamp]过期");
        }

        AccountName accountName = new AccountName(bo.getUsername());

        Account account = accountRepository.getByName(accountName);
        Assert.requireNotNull(account, ErrorCodeEnum.PARAM_ILLEGAL, "[username]不存在");

        SecEncrypt encrypt = sysEncryptRepository.getByCreateId(account.getId());

        Assert.requireNotNull(encrypt, ErrorCodeEnum.ILLEGAL_OPERATE, "[publicKey]密钥未初始化");

        String tmpString = bo.getTimestamp() + bo.getUsername();
        boolean correctSign = RsaUtils.verifySign(encrypt.getPublicKey().getValue(), tmpString, bo.getSign());
        if (!correctSign) {
            throw new BusinessException(ErrorCodeEnum.ILLEGAL_OPERATE, "[sign]错误");
        }

        // login
        CurrentAccount currentAccount = loginService.login(account);

        return securityService.createToken(currentAccount);
    }

    @Verify
    public void changePublicKey(SecSecretKeyChangeBo bo) {

        /* 测试用的参数可以在 SecEncryptLoginAppServiceTest 类生成 */

        /*
            由于更换 publicKey 需要检查 sign，更新完成后下次检查的 sign 会变。因此不怕重放攻击。
         */

        AccountName accountName = new AccountName(bo.getUsername());

        Account dbAccount = accountRepository.getByName(accountName);
        Assert.requireNotNull(dbAccount, ErrorCodeEnum.PARAM_ERROR, "[username]不存在");

        SecEncrypt dbEncrypt = sysEncryptRepository.getByCreateId(dbAccount.getId());
        Assert.requireNotNull(dbEncrypt, ErrorCodeEnum.PARAM_ERROR, "账号未初始化，请联系管理员");

        String tmpString = bo.getUsername() + bo.getPublicKey();
        // 初始化时不检查旧签名
        if (!dbEncrypt.getPublicKey().isNotInit()) {

            boolean correctSign = RsaUtils.verifySign(dbEncrypt.getPublicKey().getValue(), tmpString, bo.getSign());
            Assert.requireTrue(correctSign, ErrorCodeEnum.ILLEGAL_OPERATE, "[sign]错误");
        }

        // 检查新签名
        boolean correctSign = RsaUtils.verifySign(bo.getPublicKey(), tmpString, bo.getNewSign());
        Assert.requireTrue(correctSign, ErrorCodeEnum.ILLEGAL_OPERATE, "[newSign]错误");

        sysEncryptService.savePublicKey(dbAccount.getId(), new SecEncryptPublicKey(bo.getPublicKey()));
    }

    public String getServerPublicKey() {

        SysSecretKey sysSecretKey = sysConfigRepository.getSysSecretKey();

        return sysSecretKey.getPublicKey();
    }

}
